using System;
using System.Collections.Generic;
using System.Text;

using CILFlow;

using Mono.Cecil;

namespace CILDiffLibPrime.Collectors
{
    public class ChangePair
    {
        public CILPair Before;
        public CILPair After;

        public List<DataPredicate> DataPredicates = new List<DataPredicate>();
    }

    public class TypeChange
    {
        public Dictionary<MethodDefinition,MethodChange> ChangedMethods = new Dictionary<MethodDefinition,MethodChange>();
        public Dictionary<MethodDefinition,MethodChange> DeletedMethods = new Dictionary<MethodDefinition,MethodChange>();
        public Dictionary<MethodDefinition,MethodChange> NewMethods     = new Dictionary<MethodDefinition,MethodChange>();
    }

    public class MethodChange
    {
        public List<ChangePair> ChangePairs = new List<ChangePair>();
        public List<EntityActivity> Entities = new List<EntityActivity>();
    }

    public class EntityActivity
    {
        public string State;
        public PairEntity Entity;
        public DataFlow DataFlow = new DataFlow();
    }

    public class DataFlow
    {
        public List<ChangePair> NewUses = new List<ChangePair>();
        public List<ChangePair> DeletedUses = new List<ChangePair>();
        public List<ChangePair> ChangedUses = new List<ChangePair>();
        public List<ChangePair> NewDefs = new List<ChangePair>();
        public List<ChangePair> DeletedDefs = new List<ChangePair>();
        public List<ChangePair> ChangedDefs = new List<ChangePair>();
    }

    public class DataPredicate
    {
        public PopTree Predicate;
    }

    public class ChangePairCollector : AbstractCollector
    {
        public Dictionary<TypeDefinition, TypeChange> ChangedTypes = new Dictionary<TypeDefinition,TypeChange>();
        public Dictionary<TypeDefinition, TypeChange> DeletedTypes = new Dictionary<TypeDefinition,TypeChange>();
        public Dictionary<TypeDefinition, TypeChange> NewTypes     = new Dictionary<TypeDefinition,TypeChange>();

        TypeChange m_current;
        //Dictionary<MethodDefinition, MethodChange> m_methodChange = new Dictionary<MethodDefinition, MethodChange>();
        //Dictionary<MethodDefinition, MethodChange> m_methodDelete = new Dictionary<MethodDefinition, MethodChange>();
        //Dictionary<MethodDefinition, MethodChange> m_methodNew    = new Dictionary<MethodDefinition, MethodChange>();

        public override void OnCompareTypeBegin(TypeDefinition a, TypeDefinition b)
        {
            m_current = new TypeChange();
        }

        public override void OnCompareTypeEnd(ChangeResult result, TypeDefinition a, TypeDefinition b)
        {
            if (result == ChangeResult.Changed)
            {
                //m_current.ChangedMethods = m_methodChange;
                //m_current.DeletedMethods = m_methodDelete;
                //m_current.NewMethods     = m_methodNew;

                ChangedTypes[b] = m_current;
            }
            //m_methodChange = new Dictionary<MethodDefinition, MethodChange>();
            //m_methodDelete = new Dictionary<MethodDefinition, MethodChange>();
            //m_methodNew = new Dictionary<MethodDefinition, MethodChange>();
        }

        public override void OnDeletedTypes(List<TypeDefinition> types)
        {
            foreach (TypeDefinition type in types)
            {
                TypeChange change = new TypeChange();
                m_current = change;
                foreach (MethodDefinition md in type.Methods)
                {
                    EmitMethodDelete(md, change);
                }
                DeletedTypes[type] = change;
            }
        }

        private MatchResult<PairEntity> ExtractEntities(List<CILPair> beforePair, List<CILPair> afterPair)
        {
            List<PairEntity> before = new List<PairEntity>();
            List<PairEntity> after  = new List<PairEntity>();

            foreach (CILPair pair in beforePair )
            {
                ExtractEntity(pair, before);
            }
            foreach (CILPair pair in afterPair)
            {
                ExtractEntity(pair, after);
            }

            MatchResult<PairEntity> result = Collector.CollectMatches<PairEntity>(before, after, StructureMatch.PairEntity);
            return result;
        }

        private void ExtractEntity( CILPair pair, List<PairEntity> list )
        {
            if (pair.SourceEntity != null)
            {
                list.Add(pair.SourceEntity);
            }
            if (pair.SinkEntity != null)
            {
                list.Add(pair.SinkEntity);
            }
        }

        private List<PairEntity> ExtractEntities(List<CILPair> pairs)
        {
            List<PairEntity> list = new List<PairEntity>();
            foreach (CILPair pair in pairs)
            {
                ExtractEntity(pair, list);   
            }
            return list;
        }

        public override void OnNewTypes(List<TypeDefinition> types)
        {
            foreach (TypeDefinition type in types)
            {
                TypeChange change = new TypeChange();
                foreach (MethodDefinition md in type.Methods)
                {
                    EmitMethodNew(md,change);
                }
                NewTypes[type] = change;
            }
        }

        public override void OnCompareMethodBegin(MethodDefinition a, MethodDefinition b)
        {
        }

        public override void OnCompareMethodEnd(ChangeResult result, MethodDefinition a, MethodDefinition b)
        {
            if (result == ChangeResult.Changed)
            {
                List<CILPair> aPairs = CILPair.ExtractPairs(a);
                List<CILPair> bPairs = CILPair.ExtractPairs(b);

                MatchResult<PairEntity> entities = ExtractEntities(aPairs, bPairs);

                MethodChange change = new MethodChange();
                //m_methodChange[b] = change;
                m_current.ChangedMethods[b] = change;

                foreach( EntityVersion<PairEntity> entity in entities.CommonElements )
                {
                    EntityActivity ea = new EntityActivity();
                    ea.Entity = entity.Target;
                    ea.State = "Changed";
                    
                    ChangedChangePairs(aPairs, bPairs, entity, ea);
                    change.Entities.Add(ea);
                }

                foreach (PairEntity entity in entities.DeletedElements)
                {
                    EntityActivity ea = new EntityActivity();
                    ea.Entity = entity;
                    ea.State = "Deleted";

                    DeletedChangePairs(aPairs, ea);
                    change.Entities.Add(ea);
                }

                foreach (PairEntity entity in entities.NewElements)
                {
                    EntityActivity ea = new EntityActivity();
                    ea.Entity = entity;
                    ea.State = "New";

                    NewChangePairs(bPairs, ea);
                    change.Entities.Add(ea);
                }
            }
        }

        private void ChangedChangePairs( List<CILPair> aPairs, List<CILPair> bPairs, 
                                           EntityVersion<PairEntity> entity, EntityActivity activity )
        {
            List<CILPair> usesBefore = new List<CILPair>();
            List<CILPair> defsBefore = new List<CILPair>();
            UsesDefsFromPairs(aPairs, entity.Original, activity, usesBefore, defsBefore);
            List<CILPair> usesAfter = new List<CILPair>();
            List<CILPair> defsAfter = new List<CILPair>();
            UsesDefsFromPairs(bPairs, entity.Target, activity, usesAfter, defsAfter);

            activity.Entity = entity.Target;

            CollectChangedUses(activity, usesBefore, usesAfter);
            CollectChangedDefs(activity, defsBefore, defsAfter);
        }

        private void DeletedChangePairs(List<CILPair> pairs, EntityActivity activity)
        {
            List<CILPair> uses = new List<CILPair>();
            List<CILPair> defs = new List<CILPair>();
            UsesDefsFromPairs(pairs, activity.Entity, activity, uses, defs);

            foreach (CILPair pair in uses)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = pair;
                changePair.After = null;
                activity.DataFlow.DeletedUses.Add(changePair);
            }
            foreach (CILPair pair in defs)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = pair;
                changePair.After = null;
                activity.DataFlow.DeletedDefs.Add(changePair);
            }
        }
        private void NewChangePairs(List<CILPair> pairs, EntityActivity activity)
        {
            List<CILPair> uses = new List<CILPair>();
            List<CILPair> defs = new List<CILPair>();
            UsesDefsFromPairs(pairs, activity.Entity, activity, uses, defs);

            foreach (CILPair pair in uses)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = pair;
                changePair.After = null;
                activity.DataFlow.NewUses.Add(changePair);
            }
            foreach (CILPair pair in defs)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = pair;
                changePair.After = null;
                activity.DataFlow.NewDefs.Add(changePair);
            }
        }


        private void CollectChangedUses(EntityActivity activity, List<CILPair> usesBefore, List<CILPair> usesAfter)
        {
            MatchResult<CILPair> uses = Collector.CollectMatches<CILPair>(usesBefore, usesAfter, StructureMatch.Pair);

            foreach (EntityVersion<CILPair> commonPair in uses.CommonElements)
            {
                if (commonPair.Original.Name == commonPair.Target.Name)
                    continue;
                ChangePair changePair = new ChangePair();
                changePair.Before = commonPair.Original;
                changePair.After = commonPair.Target;
                activity.DataFlow.ChangedUses.Add(changePair);
            }

            foreach (CILPair dpair in uses.DeletedElements)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = dpair;
                changePair.After = null;
                activity.DataFlow.DeletedUses.Add(changePair);
            }

            foreach (CILPair nupair in uses.NewElements)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = null;
                changePair.After = nupair;
                activity.DataFlow.NewUses.Add(changePair);
            }
        }

        private void CollectChangedDefs(EntityActivity activity, List<CILPair> defsBefore, List<CILPair> defsAfter)
        {
            MatchResult<CILPair> defs = Collector.CollectMatches<CILPair>(defsBefore, defsAfter, StructureMatch.Pair);

            foreach (EntityVersion<CILPair> commonPair in defs.CommonElements)
            {
                if (commonPair.Original.Name == commonPair.Target.Name)
                    continue;
                ChangePair changePair = new ChangePair();
                changePair.Before = commonPair.Original;
                changePair.After = commonPair.Target;
                activity.DataFlow.ChangedDefs.Add(changePair);
            }

            foreach (CILPair dpair in defs.DeletedElements)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = dpair;
                changePair.After = null;
                activity.DataFlow.DeletedDefs.Add(changePair);
            }

            foreach (CILPair nupair in defs.NewElements)
            {
                ChangePair changePair = new ChangePair();
                changePair.Before = null;
                changePair.After = nupair;
                activity.DataFlow.NewDefs.Add(changePair);
            }
        }




        private void UsesDefsFromPairs(List<CILPair> aPairs, PairEntity entity,
            EntityActivity activity, List<CILPair> uses, List<CILPair> defs)
        {
            foreach (CILPair pa in aPairs)
            {
                if (pa.SourceEntity == entity)
                {
                    uses.Add(pa);
                }
                if (pa.SinkEntity == entity)
                {
                    defs.Add(pa);
                }
            }
        }



        public override void OnDeletedMethods(TypeDefinition parent, List<MethodDefinition> methods)
        {
            foreach (MethodDefinition md in methods)
            {
                if (md.Body != null)
                {
                    EmitMethodDelete(md, m_current);
                }
            }
        }

        private void EmitMethodDelete(MethodDefinition md, TypeChange typechange)
        {
            MethodChange mdChange = new MethodChange();

            List<CILPair> deletedPairs = CILPair.ExtractPairs(md);
            List<PairEntity> entities = ExtractEntities(deletedPairs);
            foreach (PairEntity entity in entities)
            {
                EntityActivity ea = new EntityActivity();
                ea.Entity = entity;
                ea.State = "New";

                NewChangePairs(deletedPairs, ea);
                mdChange.Entities.Add(ea);
            }
            typechange.DeletedMethods[md] = mdChange;
        }

        public override void OnNewMethods(List<MethodDefinition> methods)
        {
            foreach (MethodDefinition md in methods)
            {
                if (md.Body != null)
                {
                    EmitMethodNew(md, m_current);
                }
            }
        }

        private void EmitMethodNew(MethodDefinition md,TypeChange typechange)
        {
            MethodChange mdChange = new MethodChange();

            List<CILPair> newPairs = CILPair.ExtractPairs(md);
            List<PairEntity> entities = ExtractEntities(newPairs);
            foreach (PairEntity entity in entities)
            {
                EntityActivity ea = new EntityActivity();
                ea.Entity = entity;
                ea.State = "New";

                NewChangePairs(newPairs, ea);
                mdChange.Entities.Add(ea);
            }
            typechange.NewMethods[md] = mdChange;
        }

        public override void OnCompareFields(FieldDefinition a, FieldDefinition b)
        {
        }

        public override void OnDeletedFields(List<FieldDefinition> methods)
        {
        }

        public override void OnNewFields(List<FieldDefinition> methods)
        {
        }

        public override void OnCompareProperties(PropertyDefinition a, PropertyDefinition b)
        {
        }

        public override void OnDeletedProperties(List<PropertyDefinition> properties)
        {
        }

        public override void OnNewProperties(List<PropertyDefinition> properties)
        {
        }
    }
}